台灣軟體開發目前主要採用兩種流程,首先是瀑布式開發 (Waterfall) ,以流程為主軸,只要規則定下去,照著做就會有好產品,其中最具代表的是 CMMI,歷史跟軟體一樣久,幾年前台灣政府大力推動支持
另一種是敏捷式開發 (Agile),以人為主軸,講求的是快速從經驗中學習反應和團隊的自我管理,最知名的樣式是 Scrum,在 1990 年代異軍突起,但在台灣則是這一兩年才開始熱門起來
以一句話總結敏捷開發,就是一種應對快速變化需求的軟體開發能力,請注意敏捷開發不代表開發流程會變快,敏捷開發方法是一個快速迭代的過程,每次的迭代都能讓團隊針對過去犯下的錯誤反饋進行更正,不像傳統瀑布式開發一個流程完成才接著下一個流程,讓變動性大幅降低 敏捷測試的關鍵觀念為:客戶價值、適應需求的變化、全民測試、TDD 和 ATDD、自動化測試與其框架、持續測試
產品或服務所滿足明示或暗示需求能力的特性和特徵的集合,是定義敏捷開發品質高低的一個標準,分成:
所有成員都要抱持敏捷的精神和態度
本文件說明採用較常見的 Scrum 開發模式,其流程如圖1.1所示,以下對名詞以及流程進行說明
Figure 1.1: Scrum 開發模式流程圖
Figure 1.2: burndown chart 範例
關於驅動開發,有些容易搞混的名詞:
SBE 顧名思義,用實例來解釋需求,降低文字模糊性帶來的解讀誤差。最後會產出活文件。其中較為普遍的格式為 gherkin 語法,也就是「假如(前提),當(使用者做了什麼事),那麼(應該要得到什麼結果)」
DDD 是軟體開發藉由連接不斷進化的模組介面來應對複雜需求的方法,有系統地解構複雜的問題,很大的的一個部份是在討論商業,系統,使用者流程的分析
TDD 先寫測試再開發。除了能確保測試程式的撰寫,也有助於在開發初期釐清程式介面如何設計,整個開發流程會在單元測試、撰寫程式、重構三者間不斷循環。也就是說,先寫一個好的測試,再設法寫出能完成此條件的程式,最後調整此程式碼的結構使其更好理解、效率更好,「先把事情做對,再把事情做好」
BDD 重要精神在於能更有效地發現問題、方便協作和示範。先寫規格:採用 SBE 的作法,用人類語言來描述軟體功能和測試案例,而且可以被執行,這樣即使非技術人員也能理解—尤其是業務、開發人員可以將每個功能以 TDD 開發方式實作、且測試人員可以理解程式應該滿足的條件。主要工具為 Cucumber,其實作方法將於5.1章介紹
ATDD 由驗收測試來驅動開發,其流程如圖1.3所示
測試與開發在敏捷測試中是同步進行,不像瀑布式開發流程為分開進行,敏捷開發多加利用面對面的溝通可省去繁複的文件與時間
Figure 1.3: ATDD 測試模式流程圖
把 SBE 看成是一種 Interface (介面),TDD 看成一種 Class (類別),BDD 和 ATDD 是繼承了 TDD,同時又是兩種 SBE 的實作。此外,BDD 使用了 DDD 的若干觀念,所以 BDD 和 DDD 是 association 的關係
人非聖賢,開發過程中出現軟體錯誤是不可避免的。軟體測試就是為了發現軟體產品所存在的任何意義上的 bug,從而修正這些 bug,使軟體系統更好地滿足用戶的需求,一個好的測試能夠在第一時間發現程式中存在的錯誤,bug 拖得越晚回報,修復的成本就越高;一個好的測試是發現了至今尚未發現的錯誤的測試,這可以避免未來產品上市後潛在的可能損失
可以歸類為十大項原則,測試計畫與測試案例必須遵守
以下是專業測試團隊整理出的一些心得與經驗
測試方法依據測試原則進行,可根據對測試對象瞭解的程度,分為黑盒測試和白盒測試
根據特定用戶需求關注點,測試目標或測試原因,可以採用針對被測對象特定質量特性的測試活動,以下為一些分類
單元測試的驗證有幾種形式:
以瀑布是開發而言,為了更好的組織與實施測試工作,測試負責人需要制定測試計畫。內容包含測試流程、測試環境、測試方法、測試項目/範圍、時程安排、限制條件等,撰寫測試計畫有一些優點
以敏捷開發流程而言,測試和開發是一起進行的,測試人員和開發團隊藉由面對面溝通可以決定測試計畫的幾乎所有內容,因此測試計畫文件內容也因此大大減少,進而減少撰寫文件的時間以增加效率,測試計畫不超過一頁,或是附加在 item 裡,甚至可直接不寫
以瀑布式開發而言,撰寫測試計畫需要把握以下幾點
為了更好更有效的進行測試,保證測試工作質量,在執行測試工作之前要先設計測試案例,這是保證測試質量的核心工作,很多測試技術都可以用來指導測試案例。一份測試案例由一些測試項組成,每個測試項包含一些用例名稱、測試類型、測試條件(測試手段)、期望結果、優先序、單元測試結果、整合測試結果、jira report ID 等
在敏捷開發流程中,測試案例是 item 的一部份,或是直接在單元測試程式碼註解標記,可以不用產生一份測試案例文件
以下提供一些基本驗證測試類型與其內的測試方法,這些不是全部,希望讀者發揮想像力想出更多,測試案例除了要驗證客戶需求的功能正常運作外,也要確認不合理的輸入或操作順序會導致預期的錯誤訊息
基本驗證測試案例優先自動化,而對一些複雜的功能測試案例,可以先手工測試,直到在未來 Sprint 週期中該功能達到穩定時候再考慮自動化
配置測試環境是測試實施的一個重要階段.使用符合軟體最低需求、且在普及的作業系統,不同環境設置有時會使相同操作得出不同結果,使用 docker 可以藉由 pull 相同的 image 統一 container 環境,避免上述問題發生
自動化測試程式為敏捷測試的一個重要部分,以快速且精準的測試應對快速迭代的開發流程,以下範例為網頁程式自動化測試環境建置與方法,環境建置相對容易
在 ubuntu 安裝好 xrdp 桌面後,先在終端機依序輸入以下指令安裝中文字型相關套件
sudo apt-get install language-pack-zh-han*
sudo apt install $(check-language-support)
sudo apt-get install font-manager
接著修改兩個設定檔,將預設更改為中文環境,首先用 vi 或是 nano 編輯 /etc/default/locale 檔案
sudo nano /etc/default/locale
將內容改成
LANG="zh_TW.UTF-8"
LANGUAGE="zh_TW:zh:en_US:en"
然後修改 /etc/environment
sudo nano /etc/environment
在下方加上
LANG="zh_TW.UTF-8"
LANGUAGE="zh_TW:zh"
LC_NUMERIC="zh_TW"
LC_TIME="zh_TW"
LC_MONETARY="zh_TW"
LC_PAPER="zh_TW"
LC_NAME="zh_TW"
LC_ADDRESS="zh_TW"
LC_TELEPHONE="zh_TW"
LC_MEASUREMENT="zh_TW"
LC_IDENTIFICATION="zh_TW"
LC_ALL="zh_TW.UTF-8"
接著設定語言,輸入此指令
sudo dpkg-reconfigure locales
選擇 zh_TW.UTF-8
清理暫存空間
sudo fc-cache -fv
最後重新開機使設定生效
sudo reboot
如果打算使用 R 作為自動化測試程式寫作語言,就要安裝 R
如果打算使用 java 作為自動化測試程式寫作語言,跳過此小節
到 CRAN 官網上下載 R 語言,依照指示安裝,並且記錄安裝路徑
完成安裝後,進入 R 的主畫面,在主視窗輸入以下指令來安裝套件,一次一個
install.packages("RSelenium")
install.packages("testthat")
install.packages("odbc") # 選擇性
install.packages("DBI") # 選擇性
install.packages("readxl") # 選擇性
其中 Rselenium 套件可以模擬使用者操作瀏覽器,testthat 套件可以用來寫單元測試程式,odbc 和 DBI 作為 MSSQL 連線工具,readxl 用來讀取 excel 檔案
如果你的測試程式不需要從 MSSQL 獲得資料,跳過此小節
資料庫連嫌程式可以使 R 程式碼進行資料庫操作,如果你的電腦作業系統是 Windows,照理說 SQL Server 已經安裝完畢,你可以在搜尋欄位啟用 ODBC Data Source Administrator 並切換至 Drivers 標籤來查看,如圖3.1所示
Figure 3.1: 確認微軟資料庫驅動程式
如果你是 Mac OS 或是 Linux 系統,你可以安裝 Microsoft ODBC Driver For SQL Server
如果是為了進行自動化測試程式寫作,你還需要下載 webdirver;如果已經完成自動化測試程式,準備在 docker-jenkins 上部署,這些則由 docker-selenium 包辦
如果使用 R,根據你用於測試的網頁瀏覽器,下載 Chrome webdriver 或是 FireFox webdriver,記得下載支援最新版 chrome 或是 firefox 的 webdriver 以更貼近使用者體驗(使用者通常會將瀏覽器自動更新至最新版),並且下載 selenium server (Grid)
將下載好的 webdriver 和 selenium-server-standalone.jar 放在習慣的地方並記錄其路徑,接著到 shell 執行這個指令來啟動 selenium 伺服器:
# chrome 使用者
java -Dwebdriver.chrome.driver=<你的webdriver路徑> -jar <你的selenium-server-standalone.jar檔路徑>
# firefox 使用者
java -Dwebdriver.gecko.driver=<你的webdriver路徑> -jar <你的selenium-server-standalone.jar檔路徑>
被<>夾住的地方就是要修改的部分,連接阜預設為 4444,但你可以新增 “-port 8000” 選項將連接阜改為 8000,或是其他數字
如果使用 java,請見5.4節詳細說明
在 shell 依序輸入下面指令來新增一個運行中的 docker-selenium container,確定 container 可以啟動後,先將其關閉以避免消耗額外資源:
# 下載 docker-selenium 的 chrome image
docker pull selenium/standalone-chrome:latest
# 新增 container 來執行 docker-selenium
docker run -d -p <自動化程式連線使用的連接阜>:4444 -e NODE_MAX_INSTANCES=5 -e NODE_MAX_SESSION=5 --name selenium-server -v /dev/shm:/dev/shm selenium/standalone-chrome:latest
docker run -d -p 4444:4444 -e NODE_MAX_INSTANCES=5 -e NODE_MAX_SESSION=5 --name selenium-server -v /dev/shm:/dev/shm selenium/standalone-chrome:latest
# 查看 container 運行狀態,並確認ID
docker ps -a
# 停止指定 container
docker stop selenium-server
使用 latest 版本理由同上:更貼近使用者體驗,你可以更改連接阜來應對 R 程式的連接阜或是決定 java 程式使用的連接阜,NODE_MAX_INSTANCES 宣告每種瀏覽器最多可以有幾個分頁,NODE_MAX_SESSION 則宣告最多有多少瀏覽器可以平行運作,由 --name 參數決定此容器的名稱以方便後續呼叫,設定完成後如圖3.2所示
Figure 3.2: 新增 selenium container
使用 jenkins 建置自動化測試、建立 jira issue、 以及郵件通知,你要先準備好可以成功執行的自動化測試程式,在 shell 依序輸入以下指令來建立一個執行 docker-jenkins 的 container
# 下載最新長期穩定版 jenkins 的 image
docker pull jenkins/jenkins:lts
# 以本文寫作的時間而言,指令相當於這版本
docker pull jenkins/jenkins:2.263.2
# 新增 container 來執行 jenkins
docker run -d --user root --name myjenkins -p 8080:8080 -p 50000:50000 -v <你的docker.sock位置>:/var/run/docker.sock -v <產生測試報告的位置>:/usr/src/<專案名稱> -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts
# 查看容器運行狀態
docker ps -a
指令讓 docker-jenkins 以管理者權限執行、且映射 docker.sock 位置(以 linux 系統而言在 /var/run/docker.sock)以利於後續 docker client 安裝,進而讓這個 docker-jenkins 可以以 host 身分執行 docker 指令(例如開啟/關閉 docker-selenium、新增其它 container 等),映射你產生測試報告的位置可以讓 jenkins 找到以利後續郵件附檔形式傳送
本例使用的連接阜為 8080,執行成功後打開瀏覽器,前往 localhost:8080 會看到 jenkins 要求輸入位於特定位址的初始密碼,如圖3.3所示
Figure 3.3: jenkins 要求初始密碼
紀錄這個位置,使用下面這個指令就可以將初始密碼顯示在螢幕上
# 切換至 docker-jenkins 的 container
docker exec -it myjenkins bash
# 直接讓初始密碼顯示在螢幕上
cat <密碼位置>
使用初始密碼登入後,簡單弄一個使用者帳密,然後安裝建議的 plugins (預設)
在 docker-jenkins 的 container 內,一次一行,輸入以下指令
apt-get update
apt-get -y install apt-transport-https ca-certificates curl gnupg2 software-properties-common
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable"
apt-get update
apt-get -y install docker-ce
因為這次連線的對象是 docker-selenium,首先用這個 shell 指令找到連線至 docker-selenium 的 ip 位址
# 開啟 docker-selenium container
docker start selenium-server
docker inspect selenium-server | grep Gateway
結果如圖3.4所示
Figure 3.4: 搜尋 docker selenium ip 位址
為了證實這個 ip 確實可以使用,如果你實際啟用瀏覽器前往 <docker-selenium 包含 port 的 ip 位址>/wd/hub 這個網址,會看到一個包含 session 的表格,如圖3.5所示,確認完成後記得將此容器關閉
Figure 3.5: 確認 docker selenium ip 位址可用
前往 Manage Jenkins -> Manage plugins,通常可以在 available plugins 標籤找到 r plugin。如果找不到,那就在這裡下載最新版本的 r.hpi,下載完成後點擊 advanced 標籤,在這裡匯入 r.hpi 來安裝也可以
將你的連線程式改成這樣並且存檔
browser <- remoteDriver(
remoteServerAddr = "<docker-selenium ip>",
browserName = "<瀏覽器名稱>",
port = "<docker-selenium port 數字>"
)
在主頁左側清單點擊 new item,然後選擇 freestyle project 按確認,進入專案建置設定
將畫面往下拉,新增三個建置步驟
第一步: Execute shell –將 docker-selenium 打開
docker start selenium-server
第二步:Execute R script
testthat::test_dir("資料夾位置", reporter = "你的 Reporter")
第三步:Execute shell –關閉 docker-selenium
docker stop selenium-server
完成後按 save 回到專案頁面,點擊左側 build 就能進行建置,建置次數與結果會顯示在左下方,藍色代表成功,紅色代表失敗,黃色代表不穩定,點擊某次建置後,再點擊 console output 可以看到終端機執行結果,如圖3.6所示
Figure 3.6: jenkins 建置與查看結果
改用 RemoteWebDriver 來啟動 docker-selenium
// 開啟 chrome 瀏覽器
//System.setProperty("webdriver.chrome.driver", "<chromedriver 位置>");
//driver = new ChromeDriver();
driver = new RemoteWebDriver(new URL("<你的 docker-selenium ip>:<docker-selenium port>/wd/hub"), new ChromeOptions());
在主頁左側清單點擊 new item,然後選擇 freestyle project 按確認,進入專案建置設定
新增一個 Execute shell 建置步驟
set +e
docker start selenium-server
docker run --rm -v <你的專案位置>:/usr/src/<專案名稱> -v ~/.m2:/root/.m2 -w /usr/src/<專案名稱> maven:3.6.3-jdk-11 mvn install
cp -R /usr/src/<專案名稱> /var/jenkins_home/workspace/<這個 freestyle project 名稱>
docker stop selenium-server
因為 jenkins 執行 shell 指令時會帶有 -xe 選項,其中 -x 可以印出所有執行過程,而 -e 表示遇到錯誤訊息就中斷後續指令,set +e 用意是讓 shell 就算遇到錯誤訊息也要繼續執行,就算測試程式出現測試失敗的情況也會繼續執行
docker run 會用 image 建立新的 container 來執行指令,此例 image 是 maven:3.6.3-jdk-11 —也就是 maven 3.6.3 版並且使用 openjdk-11,指令是 mvn install —也就是建置專案,--rm 參數代表完成 container 工作後將其移除,-v 代表將資料夾與 container 位置同步,此例將位於 host 上的專案連結至 /usr/src/<專案名稱>,.m2 資料夾是 maven 專案儲存匯入 .jar 檔的地方,連結至 container 後,專案建置會找到這個資料夾並且匯入,就不用再花時間下載已經存在的 .jar 檔,-w 設定工作位址—也就是 host 上專案連結至新 container 內的位置
cp -R 可以複製整個資料夾以及內容至指定位置,由於上一行 docker run 指令執行專案產生的測試報告會出現在 /usr/src/<專案名稱> (從 host <產生報告的位置> 映射過去,這是一開始安裝 docker-jenkins 時建立的連結),將其複製到 /var/jenkins_home/workspace/<這個 freestyle project 名稱> —也就是這個專案的工作位址, docker-jenkins 就能找到產出的測試報告
完成後按 save 回到專案頁面,點擊左側 build 就能進行建置,建置次數與結果會顯示在左下方,藍色代表成功,紅色代表失敗,黃色代表不穩定,點擊某次建置後,再點擊 console output 可以看到終端機執行結果,如圖3.7所示
Figure 3.7: jenkins 建置與查看結果
在 jenkins 位置,系統管理員郵件地址輸入收信者會看到的寄信者信箱,如圖3.8所示
Figure 3.8: jenkins location 設定
在擴充電子郵件通知點擊進階按鈕,依序在以下條目輸入:
輸入完成後如圖3.9所示
Figure 3.9: extended email notification 設定
在電子郵件通知點擊進階按鈕,依序在以下條目輸入:
下方選擇寄測試信,確認信件可以傳送且信箱有確實收到測試信,你可能要先確定此機器可成功登入轉寄用的 gmail 帳號,也可能需要降低 gmail 的安全性設定使其可以讓第三方轉寄郵件,如圖3.10所示
Figure 3.10: email notification 設定
完成以上條目的設定後儲存
在建置後動作標籤下點擊新增建置後動作選單,並點擊電子郵件通知,在新增視窗的收件人條目輸入 $DEFAULT_RECIPIENTS (也就是主要郵件設定的預設接收人),如圖3.11所示
Figure 3.11: email notification 設定
接著新增可攜式電子郵件通知,點擊下方 Advanced settings… 按鈕,找到 Triggers 條目,點擊舊 trigger 右上方的紅色 X 將其移除,點擊下方 Add trigger 選擇 Always,將其內的 Send to 換成 Recipient list,再點擊進階按鈕,在 Attachments 條目新增你 workspace 裡面的測試報告檔案(以附檔形式送至信箱),如圖3.12所示
Figure 3.12: email notification 設定
設定完成後儲存,以後每次建置完成後,信箱就會收到設定好的內容
只對 java 的 cucumber 專案有效
在主頁前往 管理 Jenkins -> 管理外掛程式,通常可以在可用的標籤找到 cucumber-reports plugin。如果不行,那就在這裡下載最新版本的 cucumber-reports.hpi,下載完成後點擊進階標籤,在這裡上傳 cucumber-reports.hpi 外掛程式也可以
如果要產出 cucumber 報告,必須要使用產出的 .json 檔案,確定你的 RunCucumberTest.java 有設定 .json 產出,由於前述 Execute shell 步驟會將產出的報告複製到 workspace,jenkins 會自動找到 .json 檔並產出網頁版報告
在建置後步驟標籤下點擊新增建置後動作選單,點擊 cucumber reports,如圖3.13所示,然後點擊 save
Figure 3.13: email notification 設定
產出的 cucumber report 可以在專案首頁找到,如圖3.14所示
Figure 3.14: email notification 設定
前往管理 Jenkins -> 管理外掛程式,通常可以在可用的標籤找到 JiraTestResultReporter plugin。如果找不到,那就在這裡下載最新版本的 JiraTestResultReporter.hpi,下載完成後點擊進階標籤,在這裡上傳 JiraTestResultReporter.hpi 外掛程式也可以
輸入以上三個條目後點擊 Vaildate settings 確認 jenkins 可以連上 jira,注意此帳戶必須有權利建立新 jira issue,之後點擊進階按鈕
設定完成如圖3.15所示,按儲存
Figure 3.15: email notification 設定
JiraTestResultReporter 是利用 JUnit 產出的測試報告找出哪些測試失敗了,而 JUnit 需要 .xml 檔產出測試報告,確定你的 RunCucumberTest.java 有設定 .xml 產出
在建置後動作標籤下新增建置後動作,點擊發佈 JUnit 測試結果報告並調整設定:
點擊 Validate settings 確認連線與 key 沒有問題
設定完成如圖3.16所示,按 save
Figure 3.16: email notification 設定
回到專案首頁,建置專案後進入 test result,可以看到 JUnit 的測試結果,如果有失敗的案例也會顯示,如圖3.17所示,點擊案例左側的藍色十字查看錯誤訊息,點擊案例右側的藍色十字決定如何建立 jira issue (建立一個新 issue 或是指定一個現有的 issue),就算以後再次建置也不用擔心重複建立 issue 的問題
Figure 3.17: email notification 設定
本例使用的是 Azure DevOps 上版觸發一般 jenkins 專案建置的操作,請注意:設定此操作的帳號必須有權利對 Azure 專案的 repo 進行上版和修改的權限,也要確保 jenkins 對應連接阜是對外開放的狀態
在 jenkins 頁面點擊右上方帳號,點擊左側的設定,然後新增一個 API Token,如圖3.18所示,創建成功會出現一長串字串,將其複製到記事本上備用,小心網頁重新整理後,長字串會消失
Figure 3.18: email notification 設定
前往 https://<Azure 公司名稱>.visualstudio.com/<Azure 專案名稱>/_settings/serviceHooks,點擊 Create a new subscription,左側清單下拉點選 jenkins,按 Next,上方 Trigger 選擇 code checked in,確認下面確實是你的專案後按 Next,確認上面 action 是 Trigger generic build,Setting 欄位依序輸入 你的 jenkins 網址、你的 jenkins 帳號和剛才新建的 API token,如圖3.19所示,如果設定正確,Build 選單會出現所有這個 jenkins 帳號內的專案,選擇後按 test,確認這樣有成功觸發建置後,按 finish,有了這項設定,只要這個 repo 有上版/修改的動作就會觸發 jenkins 專案建置
Figure 3.19: email notification 設定
為了提高工作效率和工作水平,測試工作需要引進自動化測試工具,節省測試時間並提高準確度
依據測試案例,把每一個測試項寫成一個測試程式碼,這些程式碼檔名必須以 test 開頭,模擬使用者操作,寫出對應的程式,並註明預期結果,首先要匯入必要套件
# 匯入必要套件,這屬於前置動作
library(RSelenium)
library(testthat)
你的測試程式結構長這樣
# 這裡是前置動作
# 這裡開始寫用例,格式像這樣:
describe("用例名稱1". {
it("測試條件 步驟1名稱". {
# 這裡是測試步驟
})
it("測試條件 步驟2名稱", {
# 這裡是測試步驟
# 在正確的時機加入預期結果檢驗程式碼
})
...
it("沒有大括號") # 可以穿插這行,其沒有動作
...
})
...
describe("用例名稱n", {
...
})
# 這裡是結束動作,例如加入預處理資料
在測試條件裡穿插期望結果,用來了解預期的現象是否發生,形成單元測試程式碼,以下為幾個常用的
expect_equal(值1, 值2) # 預期兩個值一樣,不限於數字型態
expect_gt(值1, 值2) # 預期值1大於值2
expect_gte(值1, 值2) # 預期值1大於等於值2
expect_lt(值1, 值2) # 預期值1小於值2
expect_lte(值1, 值2) # 預期值1小於等於值2
expect_false(判斷條件) # 預期判斷條件是錯的
expect_true(判斷條件) # 預期判斷條件是對的
expect_length(清單, 數字) # 預期清單長度等於數字
在你準備好自動化測試程式碼後,把它們放在同一個資料夾內,在 R 主視窗執行這個程式碼:
testthat::test_dir("資料夾位置", reporter = ProgressReporter())
確認可以完整執行一次並且有測試統計產出,reporter 的用途就是整合測試與資料並產生報告,此例使用的是預設值,其他種類如 CheckReporter、DebugReporter、FailReporter、ListReporter、LocationReporter、MinimalReporter、MultiReporter、ProgressReporter、RstudioReporter、SilentReporter、StopReporter、SummaryReporter、TapReporter、TeamcityReporter 等各有其用處及格式,讀者可以實驗並選出最適合的 reporter
當你遇上這類測試,通常測試模組(用例)會註明刪除某資料表的特定資料,測試結束後將預處理資料加回去
首先要連線至 MSSQL 資料庫,注意連接阜必須為1433
library(odbc)
library(DBI)
# 與資料庫建立連線
con <- DBI::dbConnect(odbc::odbc(),
Driver = "資料庫連線驅動程式,例如 SQL erver",
Server = "資料庫伺服器網址",
Database = "資料庫名稱",
UID = "帳號",
PWD = "密碼",
Port = 1433)
在前置作業,根據測試模組提供的搜尋條件找到預處理資料,用變數接收以便於之後新增回資料表,注意這過程要寫在前置作業而不是測試步驟,每個 it 函數內若有定義變數,在此 it 執行完後,變數資料會消失,以下提供 MSSQL 操作相關程式碼
## 以下這些要寫在前置動作
# 讀取所有資料
#data <- dbReadTable(con, 資料表)
# 搜尋與確認欲刪除資料
results <- dbSendQuery(con, "SELECT * FROM 資料表
WHERE 特定條目 = 特定條件") # 搜尋欲刪除的資料
Sys.sleep(3) # 設定搜尋秒數
result1 <- dbFetch(results, n = 100) # 取得前100筆結果
## 以下這些要寫在測試模組內
# 刪除資料
dbExecute(con, "DELETE FROM 資料表 WHERE 特定條目 = 特定條件")
在測試結束以後,如果基於測試的某些步驟,額外新增資料至資料庫,記得先確認然後將其刪除,再將預處理資料加回,也就是說,你要檢查測試步驟新增的資料必須容易搜尋且不會找到其他原先在資料庫的資料,通常會提供資料庫表格資料明細,這樣可以知道網頁新增的資料會存存至哪個資料表,以及資料表各欄位資料的形式和意義
# 手動新增資料
dbExecute(con, "INSERT INTO 資料表 (
欄位1,
欄位2,
...
) VALUES (
值1,
值2,
...
)"
)
# 或是新增在前置動作就已儲存的預處理資料
dbWriteTable(con, "資料表", result1, append = TRUE)
實作前請確認 selenium 伺服器是開啟的(見3.6),在開啟瀏覽器之前,要先設定相關資訊
browser <- remoteDriver(
remoteServerAddr = "localhost",
browserName = "瀏覽器種類",
port = Selenium 伺服器連接阜
)
接下來就可以進行一些基本操作
browser$open() # 開啟瀏覽器
browser$navigate("網址") # 前往網站
browser$maxWindowSize() # 視窗最大化
browser$goBack() # 回上一頁
browser$goForward() # 回下一頁
browser$getCurrentUrl() # 取得網址
browser$refresh() # 重新整理
在網頁上按右鍵然後檢查網頁原始碼.在這裡,網站的每個物件都是用標籤顯示,根據欲選擇物件的特性來決定選擇方式,以 id 和 name 特性抓取物件最快最簡單也最可靠
物件 <- browser$findElement(using = "id/name/css selector/class/tag name/xpath",
value = "條件值") # 選擇物件
物件清單 <- browser$findElements(using = "name/css selestor/class/tag name/xpath",
value = "條件值") # 選擇物件清單
子物件 <- 物件$findChildElement(using = "name/css selestor/class/tag name/xpath",
value = "條件值") # 選擇子物件
子物件清單 <- 物件$findChildElements(using = "name/css selestor/class/tag name/xpath",
value = "條件值") # 選擇子物件清單
browser$mouseMoveToLocation(webElement = 物件) # 若物件未顯示,將畫面捲動至顯示物件為止
物件$getElementAttribute("id/name/class/value/...") # 獲得物件屬性值
物件$clickElement() # 點擊物件
物件$highlightElement(wait = 1) # 將物件變顯眼一段時間
物件$sendKeysToElement(list("文字", key = "按鍵")) # 對物件輸入文字和按鍵操作
輸入格物件$clearElement() # 將輸入格內文字全部清空
物件$getElementText() # 獲得物件內部文字
物件$getElementSize() # 獲得物件大小與位置
物件$getElementSize()$width # 獲得物件寬度,其他三個變數類似
如果是輸入資料,盡量輸入含有特殊字元或是非本土字元,用意是測試這些文字內容不會導致網頁產生無法預期或是不合理的狀態(例如在數字資料存入英文字),也可以測試網站對於這些不合理的字元是否有應對機制(例如數字資料內的英文字會被網頁警告)
其中一個判斷是否已登入的方法就是查看某特定 cookie 是否出現,如果特定 cookie 沒有出現,就表示尚未登入,要進行登入操作
logInflag <- TRUE
cookies <- browser$getAllCookies() # 獲得目前所有 cookie
for (i in 1:length(cookies)) {
if (cookies[[i]][["name"]] == "目標cookie") {
logInflag <- FALSE
break
}
}
if (logInflag) {
# 在這裡寫登入操作
}
有些網頁操作的重複性質很高,例如「使用左側選單在不同網站間移動」,為了方便起見,將每種重複動作寫成各個函數,以後只要出現這一步,一句程式碼呼叫函數並添加參數就能完成動作,在定義函數時適當的選擇參數有助於提高函數彈性,並使其可以進行類似的不同操作,而且寫成函數也有助於 debug,修改程式只要修函數就好,不需要跑遍整個程式碼
# 將重複步驟定義成一個函數
函數名稱 <- function(參數1 = 預設值1, 參數2, ...) {
# 在這裡定義怎麼操作
# 修改動作時只要修改這裡就好
}
...
# 在需要執行這布時,呼叫它
函數名稱(參數1值, 參數2值, ...)
有些網站會將網頁分割成不同的網頁框架,例如 R 官網是由三個網頁框架組成,使用程式操作時,你必須將瀏覽器物件切換至對應框架才能對其內的物件進行操作
formWindow <- browser$findElements(using = "css", value = "iframe") # 獲得網頁框架物件群
#length(formWindow) # 確認框架數量
XML::htmlParse(browser$getPageSource()[[1]]) # 取得目前框架內的網頁原始碼
browser$switchToFrame(formWindow[[1]]) # 切換至第一個框架
browser$switchToFrame(NA) # 切換至預設框架
這裡選單可以指排列整齊、每個子物件形式固定的清單,或是表單內的下拉式選單,一般情況下,在你選擇清單主體後,你可以藉由選擇符合規則的多個子元素來獲得子元素清單,如果選單本體為 select 標籤,套件提供專門方法來處理,內容多半為測試選單內容、長度等,選擇指定選項時,使用「名稱」而不是「位置」可以避免網頁新增更多選項而帶來的錯誤
# 以下通常用來處理一般清單
選項名稱清單 <- unlist(lapply(清單物件,
function(e) { e$getElementText() })) # 獲得選項名稱清單
清單物件[[which(選項名稱清單 == "選項值")]]$clickElement() # 選擇指定選項
# 專門處理標籤為 select 的下拉式選單
expect_false(is.element('選項值', 選單物件$selectTag()$text)) # 驗證選項沒有在選單內
expect_true(is.element('選項值', 選單物件$selectTag()$text)) # 驗證選項有在選單內
選單物件[[which(選單物件$selectTag()$text == "選項值")]]$clickElement() # 選擇指定選項
檢查特定動作會不會觸發警告視窗,並且確認警告視窗的內容正確
expect_equal(browser$getAlertText()[[1]], "警告訊息") # 確認警告訊息正確
browser$acceptAlert() # 點擊警告視窗的確認鍵
確認某物件文字內容符合條件
expect_false(物件$getElementText()[[1]] == "值") # 確認此物件文字內容不是這個值
expect_equal(物件$getElementText()[[1]], "值") # 確認此物件文字內容是這個值
expect_equal(物件$getElementAttribute("屬性")[[1]], "值") # 確認物件屬性值
基本上就是確認 tr 標籤清單的長度
tablelist <- browser$findElements(using = "tag name", value = "tr")
expect_length(tablelist, n)
找到對應的 input 標籤物件,記錄你檔案的路徑,將其使用 sendKeysToElement 方法就可以搞定
input標籤物件$sendKeysToElement(list('檔案路徑'))
在一開始開啟瀏覽器時設定
# 設定下載資訊
eCaps <- list(
chromeOptions =
list(prefs = list(
"profile.default_content_settings.popups" = 0L,
"download.prompt_for_download" = FALSE,
"download.default_directory" = "<你的下載位置>"
)
)
)
browser <- remoteDriver(
remoteServerAddr = "localhost",
browserName = "瀏覽器種類",
port = Selenium 伺服器連接阜,
extraCapabilities = eCaps
)
以下提供一些基本操作
library(readxl)
# 回傳 .xls 的所有資料表名稱陣列
excel_sheets("<你的.xls路徑>")
# 以二維陣列形式讀取資料,可指定資料表
data <- read_xls(""[, sheet = 1])
# 得到資料行數
nrow(data)
# 得到資料列標題陣列
colnames(data)
資料表無法取出或存入?網頁找不到指定物件?點擊物件的指令無效?通常是套件 bug 、網頁設計不良、或是在網頁載入完成以前就執行程式碼造成,通常重新整理網頁、實際「監督」瀏覽器動作、設定程式碼暫停時間、或是捲動頁面就可以處理,如果這些動作都沒有幫助,檢查程式碼操作是不是有問題
錯誤訊息為 “Cannot insert explicit value for identity column in table when IDENTITY_INSERT is set to OFF.” ,解決方法是先將 IDENTITY_INSERT 調成 ON,立刻將預處理資料插入,然後將其調成 OFF
dbExecute(con, "SET IDENTITY_INSERT 資料表 ON
INSERT INTO 資料表 (
欄位1,
欄位2,
...
) VALUES (
值1,
值2,
...
)
SET IDENTITY_INSERT 資料表 OFF"
)
這算是 MSSQL 一直都存在的 bug,如果發生了,就要檢查資料表欄位大小,將資料量最大的欄位移至搜尋最後方,同時注意將其寫在前置動作,畢竟要完全復原一欄大資料是非常費工的事
# 前置動作
results <- dbSendQuery(con, "SELECT
欄位1,
欄位2,
...
欄位n,
最大欄位 FROM 資料表 WHERE 指定欄位 = 條件")
Sys.sleep(1) # 設定搜尋時間
result1 <- dbFetch(results, n = 100) # 用一個獨立變數接收結果
# 這裡是測試模組程式碼
...
# 測試完成後,新增預處理資料
dbWriteTable(con, "資料表", result1, append = TRUE) # 使用上面的獨立變數
以下這段程式碼的想法是「若執行了真的沒有效果,就再試一次」,適用於網頁載入速度不穩定,或是時常未執行的程式碼
while(TRUE){
tryCatch({
# 這裡是「問題程式碼」沒執行就會錯誤的程式碼
break # 如果成功就會結束迴圈,並且繼續執行
}, error = function(msg){
# 這裡是「問題程式碼」
Sys.sleep(5) # 通常會讓程式暫停一段時間
})
}
捲動頁面,讓目標物件不會被浮動物件擋住,可以解決大部分情況,盡量避免執行多個重複捲動指令,因為重複執行網頁捲動很難控制網頁最後停在何處,捲動網頁主要依靠 mouseMoveToElement 方法
webBody <- browser$findElement(using = "css", value = "body")
webBody$sendKeysToElement(list( # 捲動網頁 往上一小段/往下一小段/往上一大段/往下一大段
key = "up_arrow/down_arrow/page_up/page_down/home/end")) # /網頁最上方/網頁最底部
Sys.sleep(0.5) # 暫停程式碼一小段時間,讓網頁捲動完成
有三個特殊符號要注意:「\」、「’」和「“」,使用 sendKeysToElement 方法如果要輸入這些特殊符號,記得把它們用跳脫字元方式處理:「\\」、「\’」和「\”」
本章節將介紹 cucumber 套件,使用 eclipse 寫作 cucumber 與 selenium 整合的 jUnit 測試程式架構並產生測試報告
Cucumber 是一個支援 BDD 的工具,支援三個重要目的:
這個 plugin 讓 eclipse 可以認得規格書檔案—也就是 .feature 檔
在最上方的功能列前往 help -> eclipse marketplace,搜尋 cucumber plugin 然後安裝
使用 maven 有許多好處:自動匯入需要的 .jar 檔,執行專案只需要指定資料夾,建立專案附送執行框架,方便與 docker-maven 整合等
前往 File -> New -> Project…,選擇 Maven Project 按 Next,按 Next,選擇 Group id 為 io.cucumber 的 archetype 按 Next,決定專案名稱按 Finish,系統就會幫你建置 cucumber 專案
編輯位於專案最上層的 pom.xml 檔,在 <dependencies> 標籤內加入 selenium-java 程式套件(至 2020 年尾,穩定的最新版本數為 3.141.59)
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
右鍵點擊位於 src/test/java/<你的專案> 內的 RunCucumberTest.java 檔 -> Run As -> JUnit Test
建立 io.cucumber 專案框架後,規格書的位置要存放在 /src/test/resources/<專案名稱> 資料夾或其子資料夾內
在資料夾點右鍵 -> New -> File,命名為「我的測試.feature」按 finish,cucumber plugin 就會產生一個預設的 .feature 檔內容
Cucumber 在測試規格書有幾個重要術語,非常容易理解,分別是 Specification、Scenario、 Step,他們的關係如下:
由於 Specification 檔必須是可執行的 (Executable),在 Cucumber 裡使用一個名叫 Gherkin 的語法結構來描述:
Feature: 一句話簡介這份規格書所涵蓋的軟體功能
對這份規格書更多的介紹 (非必要,不影響自動測試)
介紹....
介紹....
Scenario: 要測試的測試案例 1
Given 前提條件是....
When 我做了某件事....
Then 結果應該得到...
Scenario: 要測試的測試案例 2
Given 前提條件是....
When 我做了某件事....
Then 結果應該得到...
上面結構裡,Scenario 底下每一行,無論開頭關鍵字是 Given、When、Then,都代表一個要進行的 Step,在句子開頭加上 # 就是註解,編譯時會跳過這行
Gherkin 所撰寫的 Step,開頭都會帶一個關鍵字,便於理解這一個步驟的性質。
gherkin 語法支援超過 70 種語言,其中包括繁體中文,繁體中文的規格書寫作可以夾雜英文,只要在規格書第一行加上「# language: zh-TW」就可以了,例如:
# language: zh-TW
功能: 頁面凍庫選項檢查
本測試將檢查每個頁面是否正常顯示或不顯示凍庫選項
場景: 網頁登入
假如 使用 chrome 瀏覽器前往網站
當 還沒登入時輸入帳號密碼按確定
那麼 進入首頁
以下是 gherkin 關鍵字翻成繁體中文的對照表,有些關鍵字提供不只一種翻譯,值得注意的是,given、when、then 都可以用 * 表示
| English Keyword | Chinese traditional equivalent(s) |
|---|---|
| feature | 功能 |
| background | 背景 |
| scenario | 場景 |
| 劇本 | |
| scenario Outline | 場景大綱 |
| 劇本大綱 | |
| examples | 例子 |
| given | * |
| 假如 | |
| 假設 | |
| 假定 | |
| when | * |
| 當 | |
| then | * |
| 那麼 | |
| and | * |
| 而且 | |
| 並且 | |
| 同時 | |
| but | * |
| 但是 |
事實上 Cucumber 還有很多撰寫技巧有助於提高重用性,例如 Scenario Outline,要點如下
Feature: Is it Friday yet?
Everybody wants to know when it's Friday
Scenario Outline: Today is or is not Friday
Given today is Year <year>, Month <month>, Day <day>
When I ask whether it's Friday yet
Then I should be told "<answer>"
Examples:
| year | month | day | answer |
| 2020 | 1 | 1 | Nope |
| 2020 | 1 | 3 | TGIF |
| 2019 | 9 | 6 | TGIF |
| 2019 | 9 | 7 | Nope |
相同的 Step 可以用相同的一句話表示,只要是寫在同一個 cucumber 專案內就可以用共同一個函數實作,但要小心相同的一句話但操作不同的情況會導致後續的實作變得複雜,如果類似的 Step 可以用相同的方式實作,建議改成同樣句子或採用此 Scenario Outline 合併簡化
輸入的 Step 定義如果含有數字而且前後都是空白,產生實作框架時,數字就會當成參數傳入函數,型態是 Integer,如果要傳入字串,用雙引號夾住,前後留白就行了,例如:
那麼 警報資料庫新增 1 筆 "危險駕駛" "有錄一次輕度加速0下" 嚴重度 1 警報
->
@那麼("警報資料庫新增 {int} 筆 {string} {string} 嚴重度 {int} 警報")
public void 警報資料庫新增_筆_嚴重度_警報(Integer rows, String ruleType, String ruleName, Integer severity) {
//在這裡實作
}
標籤很適合用來整理 feature 和 scenario,其中一個用途是執行一部份測試,標籤的宣告方式是在@後面加上文字,並且放在 Feature(功能), Scenario(背景), Scenario Outline(場景大綱) 或是 Examples(例子) 上面,標籤不能放在 Background(背景), Given(假如), When(當) 或 Then(那麼) 上面,例如:
場景: 前往配件管理頁面
假如 左側選單已點開
當 操作選單從 "基礎資料" 點擊 "配件管理" 網頁
@HarshDriving
場景: 新增DVR配件至測試車輛
當 "DVR" 配件欄新增配件種類 "DVR" 配件型號 "通立DVR" 配件編號 "teuton臨時DVR測試" 顯示名稱 "臨時DVR"
@DoorLock
場景: 新增門位配件至測試車輛
當 "GPIO9" 配件欄新增配件種類 "Door Lock" 配件型號 "Door Lock" 配件編號 "teuton臨時DL測試" 顯示名稱 "臨時門位"
一個功能或場景可以有多個標籤,用空白隔開:
@HarshDriving @Temperature @DoorLock @LDWS
場景: 前往配件管理頁面
假如 左側選單已點開
當 操作選單從 "基礎資料" 點擊 "配件管理" 網頁
feature 檔中,子元素會繼承父元素的標籤,放在 feature 的標籤,其下方的 Scenario, Scenario Outline 和 Examples 也會繼承此標籤;放在 Scenario Outline 的標籤,其下方的 Examples 也會繼承此標籤。以下兩個例子是同一個意思:
@alarm
功能: 警報測試
@HarshDriving
場景: 網頁登入
當 還沒登入時輸入帳號密碼按確定
@alarm
功能: 警報測試
@alarm @HarshDriving
場景: 網頁登入
當 還沒登入時輸入帳號密碼按確定
只要適當的加入標籤,你可以使 cucumber 專案只執行一部份程式
第一個方法:使用 maven 參數
mvn test -Dcucumber.filter.tags="@alarm and @HarshDriving"
第二個方法:設定成環境變數
# Linux / OS X:
CUCUMBER_FILTER_TAGS="@alarm and @HarshDriving" mvn test
# Windows:
set CUCUMBER_FILTER_TAGS="@alarm and @HarshDriving"
mvn test
第三個方法:直接在 RunCucumberTest.java 宣告
@CucumberOptions(tags = "@alarm and @HarshDriving")
public class RunCucumberTest {}
你也可以執行不包含特定標籤的程式
@CucumberOptions(tags = "not @HarshDriving")
public class RunCucumberTest {}
以下舉個例子:
@AA @BB
場景: 印數1
當 列印數字 1
@AA @CC
場景大綱: 印數2
當 列印數字 <num>
@AA
例子:
|num|
|2 |
@CC
例子:
|num|
|3 |
場景: 印數4
當 列印數字 4
完成規格書寫作的時候,執行這個專案,java 會搜尋 StepDefinitions.java 內有沒有與 .feature 檔 相符的函式,如果沒有, console output 回直接給你:
@當("還沒登入時輸入帳號密碼按確定")
public void 還沒登入時輸入帳號密碼按確定() {
// Write code here that turns the phrase above into concrete actions
throw new io.cucumber.java.PendingException();
}
@那麼("進入首頁")
public void 進入首頁() {
// Write code here that turns the phrase above into concrete actions
throw new io.cucumber.java.PendingException();
}
複製這些函式到 StepDefinitions.java 的主函式內,並且 import 那些英文/繁體中文的關鍵字
有了 StepDefinition 的程式框架,接下來就是實作每個函式的內容
Assert 是 JUnit 中用來判定結果是否符合開發者預期的API,其中包含比較常用的API有
以上 message 是選擇性的輸入,如果驗證條件錯誤,主要錯誤訊息就會改為顯示 message,還是會顯示錯誤細節,這邊只列舉 assert 開頭常用的 function,雖然一般情況下 java 字串值相同的判斷方法是「字串1.equals(字串2)」,但測試仍可使用「assertEquals(字串1, 字串2)」的方法。當這些 assert 函數驗證錯誤時,會回傳 AssertionError 並停止目前場景然後繼續執行下一個場景
本傑解釋程式架構與瀏覽器操作程式
在 src/test/java 內新增二個 Package,一個稱作 common,用來存放函式共享的瀏覽器和資料;另一個稱作 seleniumPages,用來實作使用者對網頁的操作,不同頁面操作存放於不同的 .java 檔,而不是直接將所有操作寫在 StapDefinitions.java 內,這樣可以增加程式可讀性、重複利用函式、同時降低 debug 難度
所以你的專案結構大致長這樣:
--pom.xml // 決定程式主架構、 .jar 檔和編譯方式
--src
--test
--java
--專案名稱
--common
--MyDriver.java // 共享的瀏覽器和資料
--seleniumPages // 存放所有瀏覽器實作程式
--專案名稱
--RunCucumberTest.java // 用來執行專案
--StepDefinitions.java // 用來實作 .feature 的 step
--resources
--專案名稱 // 存放 .feature 的地方
--target // 通常用來存放執行檔和測試報告
selenium 程式細節下一節開始解釋
你的 MyDriver.java 如下,宣告 WebDriver 和一些可能需要的儲存資料:
import org.openqa.selenium.WebDriver;
public class Mydriver {
public static WebDriver driver;
// 可能還有其他共用變數
}
所有位於 seleniumPages 的程式碼形式如下,第三行 “extends Mydriver” 是關鍵,當所有 seleniumPages 主函式繼承同一個物件的時候,就算被外來的 StepDefinitions.java 呼叫用來新增物件,它們的操作還是會在同一個瀏覽器畫面上進行
import 專案名稱.common.Mydriver;
// 以及一些 selenium 函式
public class 主函式名稱 extends Mydriver{
public void 方法一() {
// 實作方法一
}
public void 方法二(可能有參數) {
// 實作方法二
}
...
}
StepDefinitions.java 大概長這樣,呼叫需要的 seleniumPages 內的方法來實作 .feature 的 step:
import io.cucumber.java.zh_tw.假如;
...
import 專案名稱.seleniumPages.自訂函數1;
...
public class StepDefinitions {
自訂函數1 物件1 = new 自訂函數1();
...
@關鍵字("一句 feature step")
public void 一句_feature_step() {
物件1.方法一();
}
...
}
// 設定 webdriver 位置
System.setProperty("webdriver.chrome.driver", "chromedriver位置");
// 之後就能啟動
driver = new ChromeDriver();
// 視窗最大化
driver.manage().window().maximize();
// 前往網址
driver.get("網址");
driver.navigate().to("網址"); // 或是這樣
// 到下一頁
driver.navigate().forward();
// 回上一頁
driver.navigate().back();
// 重新整理網頁
driver.navigate().refresh();
// 關閉瀏覽器
driver.quit();
// 關閉目前分頁
driver.close();
// 獲得目前網址字串
getCurrentUrl();
// 獲得目前網頁原始碼
getPageSource();
// 獲得目前網頁標題
getTitle();
// 回到本頁預設網頁框架
driver.switchTo().defaultContent();
// 直接按下警告視窗的"確定"
driver.switchTo().alert().accept();
// 從 webdriver 搜尋一個符合條件的物件,或是眾多符合條件的第一個物件
WebElement myElement = driver.findElement(By.方法(條件));
// 從 webElement 搜尋一個符合條件的子物件,或是眾多符合條件的第一個子物件
WebElement mySubElement = myElement.findElement(By.方法(條件));
// 從 webdriver 搜尋所有符合條件的物件清單
List<WebElement> myElementList = driver.findElements(By.方法(條件));
// 從 webElement 搜尋所有符合條件的子物件清單
List<WebElement> mySubElementList = myElement.findElements(By.方法(條件));
// 你也可以疊加使用搜尋:
List<WebElement> myElementList = driver.findElement(By.方法一(條件一)).findElements(By.方法二(條件二));
以下提供“條件”說明,在網頁上按右鍵然後檢查網頁原始碼.在這裡,網站的每個物件都是用標籤顯示,根據欲選擇物件的特性來決定選擇方式,以 id 和 name 特性抓取物件最快最簡單也最可靠,css 和 xpath 則是建議先看說明再使用
| 程式形式 | 條件形式 | 適用內容 |
|---|---|---|
| By.className(“a”) | class 屬性 | <? class=“a”>…</?> |
| By.cssSelector(“a > b”) | css 條件 | <a><b>…</b></a> |
| By.id(“a”) | id 屬性 | <? id=“a”>…</?> |
| By.linkText(“b”) | 超連結文字 | <a>b</a> |
| By.name(“a”) | name 屬性 | <? name=“a”></?> |
| By.partialLinkText(“b”) | 一部份超連結文字 | <a>abc</a> |
| By.tagName(“a”) | 標籤名稱 | <a>…</a> |
| By.xpath(“a”) | xpath 條件 |
// 先用瀏覽器建立 Actions 物件
Actions ac = new Actions(driver);
// 定義接下來的一連串動作,可以組合
ac.doubleClick(element); // 按兩次左鍵
ac.clickAndHold(); // 按左鍵不放
ac.dragAndDrop(Sourcelocator, Destinationlocator); // 從物件一按左鍵,直線移動至物件二,然後放開
ac.moveToElement(element); // 滑鼠移動至物件中央,如果看不到物件,捲動瀏覽器
// 動作定義完成後,執行動作
ac.build().perform();
// 傳送文字或鍵盤指令至物件,input 物件最常用,也可輸入檔案路徑來上傳檔案
element.sendKeys(key);
// 在物件中央點擊左鍵
element.click();
// 清空物件輸入值,input 物件最常用
element.clear();
// 獲得物件內的文字字串
element.getText();
// 獲得物件標籤名稱
element.getTagName();
// 獲得物件 css 值
element.getCssValue();
// 獲得物件 id 屬性,也可獲得其他標籤屬性
element.getAttribute("id");
// 獲得物件大小,可加入 .width()或 .height()方法來獲得長與寬
element.getSize();
// 物件是否顯示於畫面上
element.isDisplayed();
// 選項是否已選取,適用於選單、打勾或單選
element.isSelected();
// 獲得物件目前相對於瀏覽器左上方位置
element.getLocation();
key 可以是字串或是鍵盤指令,鍵盤指令形如 Keys.ENTER, Keys.TAB 等等
如果是輸入資料,盡量輸入含有特殊字元或是非本土字元,用意是測試這些文字內容不會導致網頁產生無法預期或是不合理的狀態(例如在數字資料存入英文字),也可以測試網站對於這些不合理的字元是否有應對機制(例如數字資料內的英文字會被網頁警告)
// 一般的程式暫停,以毫秒為單位
Thread.sleep(5000)
// 等待元素存在
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
// 等待頁面加載完成
driver.manage().timeouts().pageLoadTimeout(40, TimeUnit.SECONDS);
// 動態等待,先宣告 WebDriverWait 物件
WebDriverWait wait = new WebDriverWait(driver,30);
// 再宣告結束等待的條件
wait.until(ExpectedConditions.多選一)
// 以下為結束等待條件的幾個例子(ExpectedConditions 的方法)
// 條件不再存在時
not(ExpectedConditions.多選一)
// 警告視窗出現時
alertIsPresent()
// 可以左鍵點擊物件時
elementToBeClickable(By )
// 選單物件已選擇時
elementToBeSelected(By )
// 物件轉為不可見時
invisibilityOfElementLocated(By )
// 網頁框架存在時,注意此方法執行後會直接切換
frameToBeAvaliableAndSwitchToIt()
// 網頁可以找到該物件時
presenceOfElementLocated()
// 物件內出現指定文字時
textToBePresentInElement()
// 網頁標題符合字串時
titleIs(Str )
// 網頁標題包含字串時
titleContains(Str )
// 物件可見時
visibilityOfElementLocated(By )
// 網址符合字串時
urlToBe(String url)
顯式等待只會檢查條件是否能被執行,並不會真的執行,因為顯式等待是檢查條件是否成立,所以針對執行不穩定的誠實相對有效,同時能降低程式運行時間,若等待時間設定太短導致等待條件沒有成立,就會回傳錯誤訊息
// Select 物件的建構子 element 必須為 select 標籤
Select objSelect = new Select(element);
// 根據顯示文字選擇選項
objSelect.selectByVisibleText(“text”);
// 根據排序選擇選項
objSelect.selectByIndex(int);
// 根據選項值選擇選項
objSelect.selectByValue(“text”);
// 獲得所有選項物件
List<WebElement> list = objSelect.getOptions();
// 取消選擇所有選項,僅限於複選
objSelect.deselectAll();
// 選單是否可複選
boolean tf = objSelect.isMultiple();
其中一個判斷是否已登入的方法就是查看某特定 cookie 是否出現,如果特定 cookie 沒有出現,就表示尚未登入,要進行登入操作
// 取得所有 cookies
driver.manage().getCookies();
// 取得特定的 cookies
driver.manage().getCookieNamed(arg0);
// 刪除所有 cookies
driver.manage().deleteAllCookies();
除了 frameToBeAvaliableAndSwitchToIt() 會自動切換至該框架外,也可以用手動切換的方法
// 切換至第一個框架
driver.switchTo().frame(0)
// 切換至指定框架
driver.switchTo.frame(int frame number)
driver.switchTo.frame(string frameNameOrId)
driver.switchTo.frame(WebElement frameElement)
// 切換至母框架
driver.switchTo().parentFrame
// 切換至預設框架
driver.switchTo().defaultContent
在一開始開啟瀏覽器時就要決定
Map<String, Object> prefs = new HashMap<String, Object>();
// 定義下載資料夾於專案內的 resource 資料夾
prefs.put("download.default_directory",System.getProperty("user.dir") + File.separator + "resources");
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("prefs", prefs);
driver = new ChromeDriver(options);
熟稔 javascript 操作瀏覽器的話可以參考
// 先建立物件
JavascriptExecutor javascriptExecutor = (JavascriptExecutor) driver;
// 在物件內輸入文字
javascriptExecutor.executeScript("arguments[0].value='keyword';", searchInput);
// 捲動瀏覽器或文字方塊
js.executeScript("window.scrollBy(0,350)", "");
js.executeScript("window.scrollBy(0,-350)", "");
js.executeScript("window.scrollBy(0,document.body.scrollHeight)");
js.executeScript("arguments[0].scrollIntoView();", Element);
預設支援產出的測試報告形式包含 pretty(.txt), html, json, junit(.xml)等,其他形式可透過額外套件來產生,其中 json 格式可以用來在 jenkins 頁面上產生精美測試報告頁面、 junit 可以用來產生與 jira 連線的 issue,在 RunCucumberTest.java 內宣告報告產生的位置和設定即可
@RunWith(Cucumber.class)
@CucumberOptions(monochrome = true,
publish = true,
plugin = {"pretty:target/cucumber-pretty.txt",
"html:target/cucumber-html-report.html",
"json:target/cucumber-json-report.json",
"junit:target/cucumber-results.xml"}
)
public class RunCucumberTest {
}
monochrome 選項是為了讓 pretty 報告不會產生亂碼,publish 宣告要產出實體報告
本範例共含有四個 package,作用如下:
圖5.1是核心警報自動化測試流程圖:
Figure 5.1: 核心警報架構
本測試案例使用車號 357364080043986 和 013795001099134 ,以及teuton自動化測試圓形區域, teuton自動化測試多邊形區域,請不要刪除或修改
危險駕駛、溫度、門位和車道偏移需要另外新增配件,在新增測試車輛時會將新增的配件裝在對應 DBPort 上
危險駕駛警報因為觸發等待時間過長,目前尚未實作 DVR 錄影 Queue 的實作,只有先抓取最新 Queue 而沒有後續判斷動作,但只需確認 Queue 的車號、時間和警報條件正確即可
怠速、門位和區域共享觸發頻率與次數設定,所以它們對於這部分的實作方法相同,但是重新設定時無法直接更改值,必須將選項重新關閉與開啟
只有溫度警報規則是套用在配件上,其餘皆套用在測試車上,另外由於溫度警報執行時間過長,目前沒有確認其運作結果
離線警報分成 12 小時與 24 小時兩種,前置作業與檢查作業的標籤是分開的,請注意執行此警報的前置作業後,會因為測試車號已經存在而無法再新增,導致其他警報會在新增測試車時會出現錯誤訊息。在測試其它警報前,請先確定此警報沒有進入測試階段,離線警報的測試,在新增警報後傳送一個車機資料,然後將警報相關的訊息存在文字檔內,直到驗證警報時讀取此文字檔並且搜尋網頁日誌與資料庫驗證警報,然後將文字檔、警報以及測試車刪除
有些警報沒有結束警報後的資料,或是沒有額外資訊,例如危險駕駛警報,就會在觸發警報時一併補上
只有危險駕駛警報在決定是否錄影時決定 recordVideo 變數,其餘警報皆維持空字串
步驟與變數相關會用相同顏色外框標記
startTime 和 endTime 參數用來決定搜尋警報時間的範圍
由於同時觸發多筆警報會導致無法保證觸發先後順序,驗證警報方法會使用沒有次序的 set 而非一般有次序的 list,由 isMultipleTrigger 狀態判斷使用哪種形式判斷
Bug 是電腦或程序中存在的任何一種破壞正常運行能力的問題、錯誤、或隱藏的功能缺陷、瑕疵
bug 一般分為四個嚴重等級(可在 jira 註記):
有時會提供「建議」等級來處理測試人員所提出的建議或質疑,一般而言,越嚴重的 bug 越優先處理
Bug 狀態可以反映 bug 處於一個什麼樣的狀態,便於跟蹤(可在 jira 註記),以下為一些狀態:
若測試人員發現了 bug,以敏捷開發而言,直接面對面向開發人員說明是最理想的,若要紀錄與跟蹤 bug 處理進度,可以透過 jira 建立 issue 來描述,這裡 issue 可以是 bug、功能請求、或是任何想要追蹤的工作,以下是 jira issue 可以填寫的一些項目:
以瀑布式開發而言,測試報告是項目測試結束之後,對項目測試過程的總結,對測試的數據進行統計,對項目的測試質量進行客觀評價的文檔,測試報告的閱讀對象可是是產品,開發,測試部成員,是一個項目是否能夠結束的重要參考文件,盡可能以圖文結合方式展現出來
以敏捷開發流程而言,測試報告可以以網頁的形式釋出在內部的 web 伺服器上,並且標記比較嚴重的 bug 讓團隊清楚看到,一有變動就直接修改